﻿using System;
using System.Windows.Input;

namespace Certify.UI.ViewModel
{
    /// <summary>
    /// Generic RelayCommand implementation from http://stackoverflow.com/questions/22285866/why-relaycommand/22286816#22286816 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RelayCommand<T> : ICommand
    {
        #region Fields

        private readonly Action<T> _execute;
        private readonly Predicate<T> _canExecute;
        private UI.ViewModel.AppViewModel _viewModel;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of <see cref="DelegateCommand{T}" />. 
        /// </summary>
        /// <param name="execute">
        /// Delegate to execute when Execute is called on the command. This can be null to just hook
        /// up a CanExecute delegate.
        /// </param>
        /// <remarks> <seealso cref="CanExecute" /> will always return true. </remarks>
        public RelayCommand(Action<T> execute, UI.ViewModel.AppViewModel viewModel = null)
            : this(execute, canExecute: null)
        {
            _viewModel = viewModel;
        }

        /// <summary>
        /// Creates a new command. 
        /// </summary>
        /// <param name="execute"> The execution logic. </param>
        /// <param name="canExecute"> The execution status logic. </param>
        public RelayCommand(Action<T> execute, Predicate<T> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException("execute");
            _canExecute = canExecute;
        }

        #endregion Constructors

        #region ICommand Members

        ///<summary>
        ///Defines the method that determines whether the command can execute in its current state.
        ///</summary>
        ///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        ///<returns>
        ///true if this command can be executed; otherwise, false.
        ///</returns>
        public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute((T)parameter);

        ///<summary>
        ///Occurs when changes occur that affect whether or not the command should execute.
        ///</summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        ///<summary>
        ///Defines the method to be called when the command is invoked.
        ///</summary>
        ///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
        public void Execute(object parameter)
        {
            try
            {
                _execute((T)parameter);
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void HandleException(Exception ex)
        {
            if (_viewModel != null)
            {
                _viewModel.RaiseError(ex);
            }
            else
            {
                throw ex;
            }
        }

        #endregion ICommand Members
    }
}
